package cn.learn.config;

import cn.learn.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
import cn.learn.domain.SysPermission;
import cn.learn.properties.MySecurityConstants;
import cn.learn.properties.SecurityProperties;
import cn.learn.service.ISysLoginService;
import cn.learn.validate.ValidateCodeFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;
import java.util.List;

/**
 * security的配置信息
 *
 * @author huangyezhan
 * @date 2020年2月17日20:56:28
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Autowired
    private DataSource                     dataSource;
    @Autowired
    private UserDetailsService             userDetailsService;
    @Autowired
    private ISysLoginService               sysLoginService;
    @Autowired
    private SecurityProperties             securityProperties;
    @Autowired
    private ValidateCodeFilter             validateCodeFilter;

    /**
     * 短信验证码登录
     */
    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;


    /**
     * 推荐的密码编码方式，据说比MD5好
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        //多中加密方式，适合数据库迁移的旧密码
        //PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        return new BCryptPasswordEncoder();
    }

    /**
     * 创建rememberMe的数据库连接查询
     *
     * @return 返回操作rememberMe的sql模板对象
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //自动创建数据库表，使用一次后注释掉，不然会报错
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    /*
    //自定义配置认证用户信息和权限
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

         // 添加admin账号
         auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("123456")).
         authorities("showOrder","addOrder","updateOrder","deleteOrder","findUser","operation");
         // 添加userAdd账号
         auth.inMemoryAuthentication().withUser("userAdd").password(passwordEncoder().encode("123456")).authorities("showOrder","addOrder");
         // 如果想实现动态账号与数据库关联 在该地方改为查询数据库

       // auth.userDetailsService(myUserDetailService);
    }*/

    /**
     * 动态鉴权
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        logger.info("security权限验证启动");
        //设置自己的验证码过滤器的执行位置
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
        //设置短信验证码登录的验证
        http.apply(smsCodeAuthenticationSecurityConfig);
        //忽略druid的csrf校验
        http.csrf().ignoringAntMatchers("/druid/*")
                .and()
                //设置rememberMe的功能：1、操作的sql模板对象；2、有效时间；3、操作用户的详情信息
                //这个功能只有在游览器才有用，手机app上没有该功能
                .rememberMe()
                .tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(1 * 60)
                .userDetailsService(userDetailsService)
        ;

        http.headers()
                // 头部缓存
                .cacheControl()
                .and()
                // 防止网站被人嵌套
                .frameOptions()
                .sameOrigin()
        ;

        //获取所有需要权限才能登录的url
        List<SysPermission> permissions = sysLoginService.getAllPermissions();
        //配置动态权限
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = http.authorizeRequests();
        permissions.forEach(
                permission -> authorizeRequests
                        //要拦截的路径
                        .antMatchers(permission.getUrl())
                        //需要拦截的权限的编码
                        .hasAnyAuthority(permission.getPermissionCode())
        );

        String loginPage = securityProperties.getBrowser().getLoginPage();
        authorizeRequests
                .antMatchers("/login", loginPage, MySecurityConstants.DEFAULT_UNAUTHENTICATION_URL, "/validate/code/*").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                //自定义登录，这里定义跳转到对应的登录逻辑做判断
                .loginPage(MySecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
                //自定义登录url的检验路径，访问该url的时候，说明要检验账号密码了
                .loginProcessingUrl(MySecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
                .successHandler(myAuthenticationSuccessHandler)
                .failureHandler(myAuthenticationFailureHandler)
                .and()
                //暂时禁止
                .csrf().disable()
        //设置短信验证码登录的验证
        //.apply(smsCodeAuthenticationSecurityConfig)
        ;

    }

}
